package top.rish.universe.core.plugin;

import cn.hutool.core.util.StrUtil;
import freemarker.ext.beans.StringModel;
import freemarker.template.*;
import lombok.Data;
import org.noear.solon.Solon;
import org.noear.solon.SolonProps;
import org.noear.solon.core.AppContext;
import org.noear.solon.core.Plugin;
import org.noear.solon.core.PluginEntity;
import org.noear.solon.core.handle.ModelAndView;
import org.noear.solon.core.handle.Render;
import org.noear.solon.core.util.LogUtil;
import org.noear.solon.core.util.ScanUtil;
import org.noear.solon.view.freemarker.FreemarkerRender;
import org.noear.solon.web.staticfiles.StaticMappings;
import org.noear.solon.web.staticfiles.repository.ClassPathStaticRepository;

import java.util.*;


@Data
public abstract class UniversePlugin implements Plugin {

    public static final List<UniversePluginEntity> plugins = new ArrayList<>();
    public Properties props;
    public PluginConfig config ;
    public AppContext context;
    protected final Map<String,ModelAndView> MODEL_AND_VIEW_MAP = new HashMap<>();



    public Render getRender(){
        return null;
    }

    public final void startBefore(UniversePluginEntity pluginEntity, AppContext context) throws Throwable{
        this.setPluginPageView();
        StaticMappings.add(this.config.pluginId+"/static/",
                new ClassPathStaticRepository(this.context.getClassLoader(),
                        this.config.pluginId+"/static/"));
        UniFaasUtil.installPluginFaas(pluginEntity);
        this.start(context);
    };



    public abstract void start(AppContext context) throws Throwable;


    private PluginConfig getConfig() {
        if(this.config == null) {
            Properties properties = this.getProps();
            this.config = new PluginConfig(properties);
        }
        return this.config;
    }



    private void setPluginPageView(){
        PluginConfig config = this.getConfig();
        String pluginId = config.getPluginId();
        String version = config.getPluginVersion();
        LogUtil.global().info(StrUtil.format("setPluginPageView {}@{} starting...",pluginId,version));
        String viewSuffix = config.getViewSuffix();
        String renderName = config.getRenderName();
        ClassLoader classLoader = this.context.getClassLoader();
        String  viewPrefix = (pluginId+"/views/").replaceAll("/+","/");
        Render render = this.getRender(renderName,classLoader, viewPrefix);
        this.scanViewPathToMappingMap(classLoader,pluginId, viewPrefix,viewSuffix);

        Solon.app().http(pluginId, ctx->{
            String uri = ctx.pathNew();
            ModelAndView modelAndView = MODEL_AND_VIEW_MAP.get(uri);
            try {
                if(modelAndView!=null){
                    render.render(modelAndView, ctx);
                }
            }catch (Exception e){

            }

        });
        Solon.app().http( pluginId+"/**", ctx->{
            String uri = ctx.pathNew()
                    .replaceAll("/$","")
                    .replaceAll("/+","/");
            ModelAndView modelAndView = MODEL_AND_VIEW_MAP.get(uri);
            if(modelAndView!=null){
                // todo 插件内的所有视图
                render.render(modelAndView, ctx);
            }else{
                // todo 插件内的404默认页面
               ModelAndView error404page = MODEL_AND_VIEW_MAP.get(("/"+pluginId+"/error/404")
                        .replaceAll("/+","/"));
               if(error404page!=null){
                   render.render(error404page, ctx);
               }
            }
        });
        LogUtil.global().info(StrUtil.format("setPluginPageView {}@{} end...",pluginId,version));
    }

    private void scanViewPathToMappingMap(ClassLoader classLoader,
                                          String pluginId,
                                          String viewPrefix,
                                          String viewSuffix){
        Collection<String> nameList = ScanUtil.scan(classLoader, viewPrefix,
                n-> n.endsWith(viewSuffix)
        );
        LogUtil.global().info("scanViewPathToMappingMap");
        nameList.add(viewPrefix +"/error/404"+viewSuffix);
        nameList.add(viewPrefix +"/error/500"+viewSuffix);
        nameList.forEach(it->{
            String uri = it.replaceAll("^"+viewPrefix, "/"+pluginId+"/")
                    .replaceAll( viewSuffix+"$","")
                    .replaceAll("/+","/");
            String vn = it.replaceAll(viewPrefix,"/")
                    .replaceAll("/+","/")
                    .replaceAll("^/","");
            ModelAndView view = new ModelAndView(vn);
            MODEL_AND_VIEW_MAP.put(uri,view);
            MODEL_AND_VIEW_MAP.put(uri+".html",view);
            MODEL_AND_VIEW_MAP.put(uri+".htm",view);
            MODEL_AND_VIEW_MAP.put(uri+".ftl",view);
            MODEL_AND_VIEW_MAP.put(uri+viewSuffix,view);
            if(uri.endsWith("/index")){
                MODEL_AND_VIEW_MAP.put(uri.replaceAll("/index$",""),view);
                MODEL_AND_VIEW_MAP.put(uri.replaceAll("index$",""),view);
            }
        });
        LogUtil.global().info("scanViewPathToMappingMap end...");
    }



    private Render getRender(String renderName, ClassLoader classLoader,String viewPrefix) {
        Render r = this.getRender();
        if(r!=null){
            return r;
        }
        FreemarkerRender render = new FreemarkerRender(classLoader, viewPrefix);
        this.setFreemarkerConfig(render);
        if("FreemarkerRender".equals(renderName)){
            return render;
        }else{
            return render;
        }
    }



    private void setFreemarkerConfig(FreemarkerRender render){
        if(null!=render){
            Configuration provider = render.getProvider();
            Configuration providerOfDebug = render.getProviderOfDebug();
            setFreemarkerSyntax(providerOfDebug);
            setFreemarkerSyntax(provider);
        }

    }

    private   void setFreemarkerSyntax(freemarker.template.Configuration configuration ){
        if(configuration!=null){
            configuration.setTagSyntax(freemarker.template.Configuration.SQUARE_BRACKET_TAG_SYNTAX);
            configuration.setInterpolationSyntax(freemarker.template.Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX);
            try {
                configuration.clearTemplateCache();
                SolonProps cfg = Solon.cfg();
                String schema = cfg.getProperty("server.schema","http");
                String host = cfg.getProperty("server.host","127.0.0.1");
                String port = cfg.getProperty("server.port","80");
                String path = cfg.getProperty("server.contextPath","/");
//                LogUtil.global().info("host:"+host);
//                LogUtil.global().info("port:"+port);
//                LogUtil.global().info("path:"+path);

                configuration.setSharedVariable("pluginId",this.config.pluginId);
                configuration.setSharedVariable("host",host);
                configuration.setSharedVariable("port",port);
                configuration.setSharedVariable("path",path);
                String baseurl = schema+"://"+host+":"+port+"/"+path+"/";
                configuration.setSharedVariable("baseurl",baseurl);
                configuration.setSharedVariable("base",baseurl+(this.config.pluginId+"/").replaceAll("/+","/"));
                configuration.setSharedVariable("static",baseurl+("/"+this.config.pluginId+"/static/").replaceAll("/+","/"));
                configuration.setSharedVariable("faas",baseurl+("/faas/"+this.config.pluginId+"/").replaceAll("/+","/"));
            } catch (TemplateModelException e) {
                LogUtil.global().error(e.getMessageWithoutStackTop());
            }
            Template template = null;
            try {
                String inlineErrorPage = ("/error/page-inline-error"+this.config.viewSuffix)
                        .replaceAll("/+","/");
                template = configuration.getTemplate(inlineErrorPage, Solon.encoding());
            } catch (Exception e) {
                //忽略不计
                e.printStackTrace();
                LogUtil.global().error(e.getMessage());
            }
            if(template!=null){
                MyExceptionHandler myExceptionHandler = new MyExceptionHandler();
                myExceptionHandler.setTemplate(template);
                configuration.setTemplateExceptionHandler(myExceptionHandler);
            }
        }
    }



    private Properties getProps() {
        if(this.props != null) {
            return this.props;
        }
        Optional<UniversePluginEntity> first = plugins.stream().filter(it -> {
            return   this.getClass().getName()
                    .equals(it.getClassName())  ;
        }).findFirst();
        if(first.isPresent()){
            Properties properties = first.get().getProps();
            this.props = properties;
            return properties;
        }
        return new Properties();
    }
}
